import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";
// 引入rgbeLoad
// DataTextureLoader
// 用于加载二进制文件格式的(rgbe, hdr, ...)的抽象类。 内部使用FileLoader来加载文件， 和创建一个新的 DataTexture.
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
// 引入物理引擎
import * as CANNON from 'cannon-es';
// 用于载入glTF 2.0资源的加载器。
// glTF（gl传输格式）是一种开放格式的规范 （open format specification）， 用于更高效地传输、加载3D内容。
// 该类文件以JSON（.gltf）格式或二进制（.glb）格式提供， 外部文件存储贴图（.jpg、.png）和额外的二进制数据（.bin）。
// 一个glTF组件可传输一个或多个场景， 包括网格、材质、贴图、蒙皮、骨架、变形目标、动画、灯光以及摄像机。
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"

// 导入顶点着色器与片元着色器
import PointsBasicVertexShader from "../shader/pointsBasic/PointBasicVertexShader.glsl";
import PointsBasicFragmentShader from "../shader/pointsBasic/PointBasicFragmentShader.glsl";


// 导入官方例子中使用的水实现 需要导入材质纹理
import { Water } from 'three/examples/jsm/objects/Water2.js';


//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 300);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。
camera.position.set(0, 0, 2);
scene.add(camera); //添加相机到场景中


// 加载纹理

// 创建纹理加载器对象
const textureLoader = new THREE.TextureLoader();


// 添加环境纹理
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMapTexture = cubeTextureLoader.load([
  "textures/environmentMaps/0/px.jpg",
  "textures/environmentMaps/0/nx.jpg",
  "textures/environmentMaps/0/py.jpg",
  "textures/environmentMaps/0/ny.jpg",
  "textures/environmentMaps/0/pz.jpg",
  "textures/environmentMaps/0/nz.jpg",
]);


scene.environment = envMapTexture;
scene.background = envMapTexture;



// 加载模型纹理
const modelTexture = textureLoader.load('./models/LeePerrySmith/color.jpg');
// 加载模型的法向纹理
const normalTexture = textureLoader.load('./models/LeePerrySmith/normal.jpg')

const material = new THREE.MeshStandardMaterial({
  map:modelTexture,
  normalMap:normalTexture,
//   castShadow : true
})

const basicUnifrom = {
    uTime:{
        value:0
    }
}

material.onBeforeCompile = (shader)=>{
    console.log(shader.vertexShader);
    console.log(shader.fragmentShader);
    shader.uniforms.uTime = basicUnifrom.uTime;
    shader.vertexShader = shader.vertexShader.replace(
      '#include <common>',
      `
      #include <common>
      mat2 rotate2d(float _angle){
        return mat2(cos(_angle),-sin(_angle),
                    sin(_angle),cos(_angle));
      }
      uniform float uTime;
      `
    )
    // 旋转法相纹理顶点
    shader.vertexShader = shader.vertexShader.replace(
        '#include <beginnormal_vertex>',
        `
        #include <beginnormal_vertex>
        float angle = sin(position.y + uTime) * 0.5;
        mat2 rotateMatrix = rotate2d(angle);
        
        
        objectNormal.xz = rotateMatrix * objectNormal.xz;
        `
    )
    // 旋转顶点
    shader.vertexShader = shader.vertexShader.replace(
      '#include <begin_vertex>',
      `
      #include <begin_vertex>
    //   float angle = transformed.y*0.5;
    //   mat2 rotateMatrix = rotate2d(angle);
      
      
      transformed.xz = rotateMatrix * transformed.xz;
      `
    )
}
// 深度网格材质(MeshDepthMaterial)
// 一种按深度绘制几何体的材质。深度基于相机远近平面。白色最近，黑色最远。
// .customDepthMaterial : Material
// 在渲染到深度图的时候所用的自定义深度材质。 只能在网格中使用。 
// 当使用DirectionalLight（平行光）或者SpotLight（聚光灯光）生成影子的时候, 
// 如果你调整过顶点着色器中的顶点位置，就需要定义一个自定义深度材质来生成正确的影子。默认为undefined.
const depthMaterial = new THREE.MeshDepthMaterial({
    // .depthPacking : Constant
    // depth packing的编码。默认为BasicDepthPacking。
    depthPacking : THREE.RGBADepthPacking 
});


depthMaterial.onBeforeCompile = (shader)=>{
    console.log(shader.vertexShader);
    console.log(shader.fragmentShader);
    shader.uniforms.uTime = basicUnifrom.uTime;
    shader.vertexShader = shader.vertexShader.replace(
      '#include <common>',
      `
      #include <common>
      mat2 rotate2d(float _angle){
        return mat2(cos(_angle),-sin(_angle),
                    sin(_angle),cos(_angle));
      }
      uniform float uTime;
      `
    )
    // 旋转顶点
    shader.vertexShader = shader.vertexShader.replace(
      '#include <begin_vertex>',
      `
      #include <begin_vertex>
      float angle = sin(position.y + uTime) * 0.5;
      mat2 rotateMatrix = rotate2d(angle);
      
      transformed.xz = rotateMatrix * transformed.xz;
      `
    )
}

// 模型加载
const gltfLoader = new GLTFLoader();
gltfLoader.load('./models/LeePerrySmith/LeePerrySmith.glb',(gltf) => {
    const mesh = gltf.scene.children[0];
    mesh.castShadow = true;
    scene.add(mesh);
    // mesh.castShadow = true;
    mesh.material = material;
    mesh.customDepthMaterial = depthMaterial;
    console.log(mesh)
})

// 平面
const floor = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(20,20,32,32),
    new THREE.MeshStandardMaterial()
);
floor.position.set(0,0,-6);
floor.receiveShadow = true;
scene.add(floor);


const directionLight = new THREE.DirectionalLight('#ffffff',1);
directionLight.castShadow = true;
directionLight.position.set(0,0,200)
scene.add(directionLight);

// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
renderer.physicallyCorrectLights = true; // 新版本不用设置 物理正确光源 decay也会生效。 老版本开阴影要设置


//创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 创建坐标轴辅助器
// AxesHelper  用于简单模拟3个坐标轴的对象.
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// AxesHelper( size : Number )  size -- (可选的) 表示代表轴的线段长度. 默认为 1.
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 设置时钟
const clock = new THREE.Clock();


function render() {
    let elapsedTime = clock.getElapsedTime();
    // material.uniforms.uTime.value = elapsedTime;
    basicUnifrom.uTime.value = elapsedTime;
    controls.update();
    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render();

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})


